package com.borya.util.cipher;

import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Base64.Decoder;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.log4j.Logger;

public class EncryptUtils {

	private static final Logger log = Logger.getLogger(EncryptUtils.class);

	/**
	 * 数据加密,先对数据进行加密，然后将加密后的数据进行base64编码
	 *
	 * @param userId
	 * @param timestamp
	 * @param packagename
	 * @param str
	 * @return base64编码后的数据
	 */
	public static String encrypt(String userId, long timestamp, String packagename, String str) {
		byte[] key;
		try {
			byte[] packageName = packagename.getBytes("UTF-8");
			byte[] tmsi = userId.getBytes("UTF-8");

			key = getKey(packageName, tmsi, timestamp);
			// System.out.print("key:"+new String(key));
			// printBuffer(key);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			return "";
		}

		return encrypt(key, str);
	}

	public static String encryptNew(String userId, long timestamp, String packagename, String str) {
		byte[] key;
		try {
			byte[] packageName = packagename.getBytes("UTF-8");
			byte[] tmsi = userId.getBytes("UTF-8");

			key = getNewKey(packageName, tmsi, timestamp);
			// System.out.print("key:"+new String(key));
			// printBuffer(key);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			return "";
		}

		return encrypt(key, str);
	}

	public static String encrypt(byte[] key, String str) {
		try {
			byte[] iv = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
					0x0e, 0x0f };

			byte[] b = str.getBytes("UTF-8");
			byte[] in;
			if ((b.length % 16) == 0) {
				in = b;
			} else {
				in = new byte[(b.length / 16 + 1) * 16];
				System.arraycopy(b, 0, in, 0, b.length);
			}

			SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
			IvParameterSpec ivSpec = new IvParameterSpec(iv);
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
			byte[] encrypted = cipher.doFinal(in);
			// System.out.print("encrypt:");
			// printBuffer(encrypted);
			in = null;
			String base64_str = new String(Base64.getEncoder().encode(encrypted), "UTF-8").replaceAll("\r|\n", "");
			return base64_str;
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			return "";
		}
	}

	/**
	 * 数据解密
	 *
	 * @param userId
	 * @param timestamp
	 * @param packagename
	 * @param base64_str
	 * @return 明文数据
	 */
	public static String decrypt(String userId, long timestamp, String packagename, String base64_str) {
		byte[] key;
		try {
			byte[] packageName = packagename.getBytes("UTF-8");
			byte[] tmsi = userId.getBytes("UTF-8");

			key = getKey(packageName, tmsi, timestamp);

		} catch (Exception e) {
			log.error(e.getMessage(), e);
			return "";
		}

		return decrypt(key, base64_str);
	}

	public static String decryptNew(String userId, long timestamp, String packagename, String base64_str) {
		byte[] key;
		try {
			byte[] packageName = packagename.getBytes("UTF-8");
			byte[] tmsi = userId.getBytes("UTF-8");

			key = getNewKey(packageName, tmsi, timestamp);

		} catch (Exception e) {
			log.error(e.getMessage(), e);
			return "";
		}

		return decrypt(key, base64_str);
	}

	public static String decrypt(byte[] key, String base64_str) {
		try {
			// 解密
			// BASE64Decoder decoder = new BASE64Decoder();
			Decoder decoder = Base64.getDecoder();
			byte[] in = decoder.decode(base64_str);
			if (in.length % 16 != 0) {
				return "";
			}

			byte[] iv = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
					0x0e, 0x0f };
			SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
			IvParameterSpec ivSpec = new IvParameterSpec(iv);
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
			byte[] decrypted = cipher.doFinal(in);
			int realDataMaxIndex = 0;
			for (int i = decrypted.length - 1; i > 0; i--) {
				if (decrypted[i] == 0) continue;
				realDataMaxIndex = i;
				break;
			}
			if (realDataMaxIndex != decrypted.length - 1) {
				byte[] replaced = new byte[realDataMaxIndex + 1];
				System.arraycopy(decrypted, 0, replaced, 0, realDataMaxIndex + 1);
				return new String(replaced, StandardCharsets.UTF_8).replaceAll("\r|\n", "");
			} else {
				return new String(decrypted, StandardCharsets.UTF_8).replaceAll("\r|\n", "");
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			return "";
		}
	}

	private static byte[] getNewKey(byte[] packageName, byte[] tmsi, long timestamp) {
		if (packageName == null || tmsi == null) {
			return null;
		}

		long packageCode = hashCode(packageName);
		long tmsiCode = hashCode(tmsi);

		int placeOne = (int) (timestamp % 10);
		int placeTwo = (int) (tmsiCode % 10);

		packageCode = packageCode >>> placeOne;
		timestamp = timestamp << placeTwo;

		long middleOne = ~packageCode | placeTwo;
		long middleTwo = tmsiCode & packageCode;

		long newCode = timestamp | (middleOne << 32) | middleTwo;

		if (!ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN)) {
			newCode = swichEndian(newCode);
			timestamp = swichEndian(timestamp);
		}

		byte[] newCodeData = new byte[8];
		byte[] timestampData = new byte[8];

		for (int i = 0; i < newCodeData.length; i++) {
			newCodeData[i] = new Long(newCode & 0xff).byteValue();
			newCode = newCode >>> 8;
		}
		for (int i = 0; i < timestampData.length; i++) {
			timestampData[i] = new Long(timestamp & 0xff).byteValue();
			timestamp = timestamp >>> 8;
		}
		byte[] key = new byte[16];
		for (int i = 0; i < 16; i++) {
			if (i % 2 == 0) {
				if (timestampData[2] == 0) {
					key[i] = (byte) 0;
				} else {
					key[i] = (byte) (timestampData[i / 2] % timestampData[2]);
				}
			} else {
				if (newCodeData[5] == 0) {
					key[i] = (byte) 0;
				} else {
					key[i] = (byte) ((newCodeData[(i - 1) / 2]) % newCodeData[5]);
				}
			}
		}
		return key;
	}

	private static byte[] getKey(byte[] packageName, byte[] tmsi, long timestamp) {
		if (packageName == null || tmsi == null) {
			return null;
		}

		long packageCode = hashCode(packageName);
		long tmsiCode = hashCode(tmsi);

		int placeOne = (int) (timestamp % 10);
		int placeTwo = (int) (tmsiCode % 10);

		packageCode = packageCode >>> placeOne;
		timestamp = timestamp << placeTwo;

		long middleOne = ~packageCode | placeTwo;
		long middleTwo = tmsiCode & packageCode;

		long newCode = timestamp | (middleOne << 32) | middleTwo;

		if (!ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN)) {
			newCode = swichEndian(newCode);
			timestamp = swichEndian(timestamp);
		}

		byte[] newCodeData = new byte[8];
		byte[] timestampData = new byte[8];

		for (int i = 0; i < newCodeData.length; i++) {
			newCodeData[i] = new Long(newCode & 0xff).byteValue();
			newCode = newCode >>> 8;
		}
		for (int i = 0; i < timestampData.length; i++) {
			timestampData[i] = new Long(timestamp & 0xff).byteValue();
			timestamp = timestamp >>> 8;
		}
		byte[] key = new byte[16];
		for (int i = 0; i < 16; i++) {
			if (i % 2 == 0) {
				key[i] = timestampData[i / 2];
			} else {
				key[i] = newCodeData[(i - 1) / 2];
			}
		}
		return key;
	}

	private static long hashCode(byte[] bytes) {
		if (bytes == null) {
			return 0;
		}
		long hash = 0;
		for (int i = 0; i < bytes.length; i++) {
			hash = 31 * hash + ub2i(bytes[i]);
			if (hash > 4294967295L) {
				hash = hash % 4294967296L;
			}
		}
		return hash;
	}

	private static int ub2i(byte b) {
		if (b > 0) {
			return b;
		}
		return b + 256;
	}

	private static long swichEndian(long value) {
		return ((value & 0x00000000000000ffL) << 56) | ((value & 0x000000000000ff00L) << 40)
				| ((value & 0x0000000000ff0000L) << 24) | ((value & 0x00000000ff000000L) << 8)
				| ((value & 0x000000ff00000000L) >>> 8) | ((value & 0x0000ff0000000000L) >>> 24)
				| ((value & 0x00ff000000000000L) >>> 40) | ((value & 0xff00000000000000L) >>> 56);
	}

	public static void main(String[] args) {
		// String userId = "18050509871";
		// long timestamp = 1482377515557L;
		// String package_name = "hello";
		// String str = "I love you";
		// String encrypt = "X0yjLGe0WXfceeHAmjKKuw==";
		//// encrypt(userId, timestamp, package_name, str);
		//// System.out.println(encrypt);
		//
		// String decrypt = decrypt(userId, timestamp, package_name, encrypt);
		// System.out.println("解密后的数据：" + decrypt);
	}

}